package cn.com.stary1993.kite.captcha.support.simple;

import cn.com.stary1993.kite.captcha.config.CaptchaProperties;
import cn.com.stary1993.kite.captcha.config.SimpleImageCaptchaProperties;
import cn.com.stary1993.kite.captcha.store.CacheStore;
import cn.com.stary1993.kite.captcha.support.CaptchaValidException;
import cn.com.stary1993.kite.captcha.support.simple.generator.SimpleImageCaptchaGenerator;
import cn.com.stary1993.kite.captcha.vo.CaptchaResponse;
import cn.com.stary1993.kite.captcha.vo.SimpleCaptchaVO;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.OutputStream;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 默认简单图形验证码
 *
 * @author stary1993
 */
@Slf4j
@Data
public class DefaultSimpleImageCaptcha extends SimpleImageCaptcha {

    private CaptchaProperties target;

    /**
     * 缓冲存储.
     */
    private CacheStore cacheStore;

    private SimpleImageCaptchaGenerator generator;

    public DefaultSimpleImageCaptcha(CacheStore cacheStore, CaptchaProperties target,
                                     SimpleImageCaptchaGenerator generator) {
        setCacheStore(cacheStore);
        this.target = target;
        this.generator = generator;
    }

    @Override
    public CacheStore getCacheStore() {
        return this.cacheStore;
    }

    @Override
    public void setCacheStore(CacheStore cacheStore) {
        this.cacheStore = cacheStore;
    }

    @Override
    public CaptchaResponse<SimpleCaptchaVO> generate() {
        return generate(target.getSimple().getType());
    }

    /**
     * 生成验证码
     *
     * @param type type类型
     * @return
     */
    @Override
    public CaptchaResponse<SimpleCaptchaVO> generate(SimpleImageCaptchaType type) {
        return this.generate(type, null);
    }

    /**
     * 生成验证码
     *
     * @param type      类型
     * @param timestamp 时间戳
     * @return CaptchaResponse<SimpleCaptchaVO>
     */
    @Override
    public CaptchaResponse<SimpleCaptchaVO> generate(SimpleImageCaptchaType type, String timestamp) {
        if (type == null) {
            type = SimpleImageCaptchaType.spec;
        }
        AbstractCaptcha captcha = generator.getCaptcha(type);
        captcha.createCode();
        String id = generatorId();
        String code = captcha.getCode();
        cacheVerification(id, type.name(), timestamp, code);
        CaptchaResponse<SimpleCaptchaVO> response = new CaptchaResponse<>();
        response.setId(id);
        SimpleCaptchaVO captchaVO = new SimpleCaptchaVO();
        captchaVO.setId(id);
        String base64Header = Objects.equals(type, SimpleImageCaptchaType.gif) ? BASE64_GIF : BASE64_JPG;
        captchaVO.setImage(base64Header + captcha.getImageBase64());
        response.setCaptcha(captchaVO);
        return response;
    }

    /**
     * 输出流
     *
     * @param out
     */
    @Override
    public void out(String timestamp, OutputStream out) {
        SimpleImageCaptchaType type = target.getSimple().getType();
        if (type == null) {
            type = SimpleImageCaptchaType.spec;
        }
        out(type, timestamp, out);

    }

    /**
     * 输出流
     *
     * @param out
     */
    @Override
    public void out(SimpleImageCaptchaType type, String timestamp, OutputStream out) {
        AbstractCaptcha captcha = generator.getCaptcha(type);
        captcha.createCode();
        String code = captcha.getCode();
        cacheVerification(null, type != null ? type.name() : null, timestamp, code);
        captcha.write(out);
    }

    /**
     * 验证
     *
     * @param inputCode 用户验证码
     * @return
     */
    @Override
    public boolean verify(String inputCode) {
        return this.verify(null, inputCode);
    }

    @Override
    public boolean verify(String timestamp, String inputCode) {
        SimpleImageCaptchaType type = target.getSimple().getType();
        if (type == null) {
            type = SimpleImageCaptchaType.spec;
        }
        return verify(null, type, timestamp, inputCode);
    }

    @Override
    public boolean verify(SimpleImageCaptchaType type, String timestamp, String inputCode) {
        return verify(null, type, timestamp, inputCode);
    }

    @Override
    public boolean verify(String id, SimpleImageCaptchaType type, String timestamp, String inputCode) {
        String key = getKey(id, type.name(), timestamp);
        Object cacheCode = cacheStore.getAndRemoveCache(key);
        if (Objects.isNull(cacheCode)) {
            throw new CaptchaValidException("验证码已失效");
        }
        JSONObject data = new JSONObject(cacheCode);
        log.debug("cache Captcha : {}", data);
        return this.generator.verify(data.getStr(CaptchaProperties.PREFIX), inputCode);

    }

    protected String generatorId() {
        return IdUtil.fastUUID();
    }

    /**
     * 缓存验证码
     *
     * @param id
     * @param type
     * @param timestamp
     * @param code
     */
    protected void cacheVerification(String id, String type, String timestamp, String code) {
        SimpleImageCaptchaProperties prop = target.getSimple();
        JSONObject data = new JSONObject();
        data.set(CaptchaProperties.PREFIX, code);
        if (!getCacheStore().setCache(getKey(id, type, timestamp), data.toString(), prop.getExpire(),
                TimeUnit.MILLISECONDS)) {
            log.error("缓存验证码数据失败， id={}, code={}", id, code);
            throw new CaptchaValidException(type, "缓存验证码数据失败");
        }
    }

    protected String getKey(String id, String type, String timestamp) {
        SimpleImageCaptchaProperties prop = target.getSimple();
        String key = target.getPrefix().concat(":").concat(prop.getKeyPrefix());
        if (StrUtil.isNotBlank(id)) {
            key = key.concat(":").concat(id);
        }
        if (StrUtil.isNotBlank(type)) {
            key = key.concat(":").concat(type);
        }
        if (StrUtil.isNotBlank(timestamp)) {
            key = key.concat(":").concat(timestamp);
        }
        return key;
    }

}
